iT邦幫忙

2023 iThome 鐵人賽

DAY 6
2
Software Development

CRUD仔的一生(上集)系列 第 7

[ACID] 樂觀鎖(MVCC In Postgres)

  • 分享至 

  • xImage
  •  

Postgres MVCC 原理

前言

前面介紹了樂觀鎖的原理,這裡將介紹在 postgres 中,是如何利用樂觀鎖的原理來建構 MVCC DB 的。

CRUD在MVCC下的長相

我們先建立好一張table叫table1,裡面只有name這個欄位。

CREATE TABLE table1 (
	"name" varchar NOT NULL
);
remark: 以下範例使用 PostgreSQL 12.3

隱藏欄位

其實 Postgres 每個表中都有一些隱藏欄位,
我們可以嘗是在任何一張 table query 出那些欄位

select ctid,xmin,xmax,cmin,cmax,id from XXX_TABLE

欄位 意思
ctid 在表中的邏輯位置
xmin born 建立 record 時,當前 tx 的 id
xmax die 刪除 record 時,當前 tx 的 id,default=0
cmin 插入事務中的命令標識符
cmax 刪除交易中的命令標識符

ctid: 可以幫我們看一下是否有新的 record 建立出來
xmin: 看該筆 record 是由哪個 tx 所建立的,這裡填上 txId
xmax: 看該筆 record 是由哪個 tx 所刪除的,這裡填上 txId
cmin:插入事務中的命令標識符(從 0 開始)
cmax:刪除交易中的命令標識符(從 0 開始)

後續的介紹我們這裡只需要特別注意ctid與xmax,xmin這三個key即可。

我們知道每次的操作都是一個 transaction,那麼該如何得知目前的 transactionId 呢?
可以下
select txid_current()

大概知道上面各個欄位做什麼後,接下來開始解析 CRUD 的操作會有什麼變化吧!

CREATE

先來看看insert後會有什麼變化

begin;
SELECT txid_current(); --735
insert into table1 (name) values ('a' );
select *,ctid,xmin,xmax from table1;
commit;
name ctid xmin xmax
a (0,1) 753 0
可以看到ctid與xmin被賦予了值,而xmin就是insert資料的txid

READ

begin;
select *,ctid,xmin,xmax from table1;
commit;
name ctid xmin xmax
a (0,1) 753 0
除了SELECT txid_current();之外,
一般的select都不會被assign一個txid.
因為沒有做任何修改(write).

DELETE

--tx1
begin;
SELECT txid_current(); --754
DELETE FROM table1 WHERE name = 'a';

因為delete commit後就無法利用一般的select查到,
所以我們趁著還沒commit之前,開另外一個tx來做select。

--tx2
select *,ctid,xmin,xmax from table1;
name ctid xmin xmax
a (0,1) 753 754

可以看到xmax被賦予了值,而xmax就是DELETE資料的txid。

UPDATE

--tx1
begin;
SELECT txid_current(); --755
UPDATE  table1 set name='uu' where name='a';
select *,ctid,xmin,xmax from table1;
name ctid xmin xmax
uu (0,2) 755 0
可以看到ctid為(0,2),xmin為755,就如同insert一筆新的紀錄。

因為UPDATE commit後就無法利用一般的select查被標誌上delete的紀錄,
我們趁著還沒commit之前,開tx2來做select。

--tx2
select *,ctid,xmin,xmax from table1;
name ctid xmin xmax
a (0,1) 753 755
可以看到ctid(0,1)仍在,xmax被標上755,就如同DELET紀錄般。

所以update的動作我們可以被視為:
insert一筆新紀錄+delete一筆舊紀錄。

Remark: 如果兩個tx同時update,後面update的會需要等前面tx的commit完後才能做update。

ROLLBACK

至於rollback呢?
我們就拿剛剛還沒COMMIT的UPDATE來做ROLLBACK.
然後再select *,ctid,xmin,xmax from table1;

name ctid xmin xmax
a (0,1) 753 755

ctid(0,1)雖然沒有被刪除掉也仍然可以被查看,但是他的xmax居然還有755的標記,這是什麼原因呢?我們先繼續往下看。

我們再新增一筆紀錄
insert into table1 (name) values ('b');

name ctid xmin xmax
a (0,1) 753 755
b (0,3) 755 0
ctid從(0,3)開始了,所以ctid(0,2)的插入也成功rollback了.

rollback沒看到ctid(0,2)很合理,但是ctid(0,1)的xmax被填上了755,所以他到底是被刪除了還是還沒呢?
這時就要討論一下postgres MVCC的可見性判斷了。

可見性判斷

邏輯大概是這樣
每個有做過write的tx都會有一個自己的txid.
而如果只有xmin代表新增的紀錄,如果同時有xmin與xmax代表被刪除的紀錄.
每一筆的紀錄,在操作時,都會被記錄到一個clog的檔案之中,
用來表示該筆紀錄是否有被其他tx正在做修改。

clog (commit log)

在每個tx的執行之中,都會在每筆紀錄clog,來表示該表紀錄正處於什麼樣的狀態之中。
#define TRANSACTION_STATUS_IN_PROGRESS=0x00 正在進行中
#define TRANSACTION_STATUS_COMMITTED=0x01 已提交(commit)
#define TRANSACTION_STATUS_ABORTED=0x02 已回滾(rollback)
#define TRANSACTION_STATUS_SUB_COMMITTED=0x03 子事務已提交

判斷邏輯

主要會先判斷clog,

  1. TRANSACTION_STATUS_IN_PROGRESS,如果xmin與xmax不是自己的txid那麼就不可見。
  2. TRANSACTION_STATUS_COMMITTED,進入下一個判斷。
    1. txid>xmin or xmax:代表舊有的tx,該是什麼樣就什麼樣。
    2. txid=xmin or xmax:代表目前自己操作的tx,該是什麼樣就什麼樣。
    3. txid<xmin or xmax:代表未來的tx,不可見。
  3. TRANSACTION_STATUS_ABORTED,進入下一個判斷。
    1. txid>xmin or xmax:代表舊有的tx,該是什麼樣就不要什麼樣。
    2. txid=xmin or xmax:代表目前自己操作的tx,該是什麼樣就不要什麼樣。
    3. txid<xmin or xmax:代表未來的tx,不可見。
      而剛剛
name ctid xmin xmax
a (0,1) 753 755
b (0,3) 755 0

的clog這兩筆紀錄被標註上了TRANSACTION_STATUS_ABORTED,
因此,代表著被新增的要不可見,被刪除的要可見。

結語

postgres 透過 xmin/xmax 與 commit 的狀態來判斷是否為該 tx 可以用的資料。
我們可看到當 rollback 與刪除時,這些標記都還在,頻繁操作的表會累積大量標記,佔用硬碟空間,
postgres 的解決方法是提供 vacuum 命令來清理過期的數據,也會在後續的單元中介紹。

參考資料

  1. PostgreSQL 的 MVCC 机制解析
  2. PostgreSQL Commit Log(clog)
  3. PostgreSQL 中的 MVCC (01)——基本可见性判断
  4. PostgreSQL MVCC可见性判断

上一篇
[ACID] 樂觀鎖(OptimisticLock)
下一篇
[ACID] 樂觀鎖(MVCC In MySQL)
系列文
CRUD仔的一生(上集)32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言